#include "checkers/RedundantNullCheck.h"

extern ReportSpace::Report external_bug_report;

void RedundantNullCheck::readConfig() {
  std::unordered_map<std::string, std::string> ptrConfig =
      configure->getOptionBlock("RedundantNullChecker");
  pointer_analysis = ptrConfig.find("pointer_analysis")->second;
}

std::string RedundantNullCheck::getString_of_Expr(Expr *expr) {
  LangOptions L0;
  L0.CPlusPlus = 1;
  std::string buffer1;
  llvm::raw_string_ostream strout1(buffer1);
  expr->printPretty(strout1, nullptr, PrintingPolicy(L0));
  return strout1.str();
}

std::string RedundantNullCheck::exitNullCheck(ASTFunction *fun,
                                              string arg_str) {
  std::string fal = "";
  if (fun == NULL)
    return fal;

  const FunctionDecl *funDecl = manager->getFunctionDecl(fun);
  clang::ASTContext &astcontext = funDecl->getASTContext();
  std::unique_ptr<CFG> &funcfg = manager->getCFG(fun);

  std::set<const PTAVar *> alias_set;
  alias_set.clear();

  for (CFG::iterator iter = funcfg->begin(); iter != funcfg->end(); iter++) {
    CFGBlock *block = *iter;
    if (block == nullptr)
      continue;
    Stmt *it = block->getTerminator().getStmt();
    if (it == nullptr)
      continue;

    if (it->getStmtClass() == Stmt::IfStmtClass) {
      Expr *condexpr = ((IfStmt *)it)->getCond();
      if (condexpr == nullptr)
        continue;
      if (condexpr->getStmtClass() == Stmt::BinaryOperatorClass) {
        BinaryOperator *binaryOpExpr = dyn_cast<BinaryOperator>(condexpr);
        Expr *exprLHS = binaryOpExpr->getLHS();
        string lexp = getString_of_Expr(exprLHS);
        Expr *exprRHS = binaryOpExpr->getRHS();
        string rexp = getString_of_Expr(exprRHS);
        std::string lexploc =
            exprLHS->getExprLoc().printToString(astcontext.getSourceManager());

        /* Note: only condiser the simple situation "p == null" or "p != null"
         */
        if (std::strcmp(rexp.c_str(), "__null") == 0) {
          QualType lexp_type = exprLHS->getType();
          if (lexp_type.getTypePtr()->isPointerType()) {
            // situation 1: pass(p), check(p)
            if (std::strcmp(lexp.c_str(), arg_str.c_str()) == 0) {
              return lexploc;
            }

            // situation2: pass(p), n=p, check(n)
            if (pointer_analysis == "true") {
              const PTAVar *var = nullptr;
              auto vars = fun->getVariables();
              for (auto varItem : vars) {
                if (varItem->isPointerOrReferenceType() &&
                    varItem->getName() == arg_str) {
                  auto ptr_vd = manager->getVarDecl(varItem);
                  if (ptr_vd == nullptr)
                    return fal;
                  var = p2a->get_pvm().getPTAVar(p2a->getPTAVarKey(ptr_vd));
                  if (var != nullptr) {
                    try {
                      alias_set = p2a->get_alias_in_func(
                          manager->getFunctionDecl(fun), var);
                    } catch (int es) {
                      cout << "[Error], failed to get alias in fun "
                           << fun->getFunctionName() << es << endl;
                    }
                    if (alias_set.size() == 0) {
                      return fal;
                    }
                    for (auto aliasItem = alias_set.begin();
                         aliasItem != alias_set.end(); aliasItem++) {
                      if (std::strcmp(lexp.c_str(),
                                      (*aliasItem)->get_name().c_str()) == 0) {
                        return lexploc;
                      }
                    }
                  }
                }
              }
            }

            // TODO: situation3: pass(p), n=compute(p), check(n)
          }
        }
      }
    }
  }

  return fal;
}

vector<string> split(const string &str, const string &pattern) {
  vector<string> res;
  if (str == "")
    return res;
  string strs = str + pattern;
  size_t pos = strs.find(pattern);
  while (pos != strs.npos) {
    string temp = strs.substr(0, pos);
    res.push_back(temp);
    strs = strs.substr(pos + 1, strs.size());
    pos = strs.find(pattern);
  }

  return res;
}

void RedundantNullCheck::visitFromEntry() {
   
  std::vector<ASTFunction *> unvisited_queue;
  std::vector<ASTFunction *> visited_queue;
  std::vector<ASTFunction *> topLevelFuncs = call_graph->getTopLevelFunctions();

  for (auto fun : topLevelFuncs) {
    if (fun == NULL) {
      continue;
    }

    const FunctionDecl *funDecl = manager->getFunctionDecl(fun);
    if (funDecl == nullptr || funDecl->hasBody() == false ||
        funDecl->isInStdNamespace() == true) {
      continue;
    }

    unvisited_queue.push_back(fun);

    while (unvisited_queue.size() > 0) {
      ASTFunction *caller = unvisited_queue.back();
      unvisited_queue.pop_back();
      visited_queue.push_back(caller);
      checkFun(caller);
      for (ASTFunction *callee : call_graph->getChildren(caller)) {
        if (callee == NULL)
          continue;
        vector<ASTFunction *>::iterator iter =
            find(visited_queue.begin(), visited_queue.end(), callee);
        if (iter == visited_queue.end()) {
          const FunctionDecl *calleeDecl = manager->getFunctionDecl(callee);
          if (calleeDecl == nullptr || calleeDecl->hasBody() == false ||
              calleeDecl->isInStdNamespace() == true) {
            continue;
          }
          unvisited_queue.push_back(callee);
        }
      }
    }
  }
}

void RedundantNullCheck::checkFun(ASTFunction *caller) {
  const FunctionDecl *callerDecl = manager->getFunctionDecl(caller);
  if (callerDecl == nullptr || callerDecl->hasBody() == false ||
      callerDecl->isInStdNamespace() == true) {
    return;
  }

  /*begin check argument type is pointer?*/

  for (int i = 0; i < callerDecl->getNumParams(); i++) {
    const ParmVarDecl *par = callerDecl->getParamDecl(i);
    if (par == nullptr)
      continue;

    const QualType par_type = par->getOriginalType();
    if (!par_type.getTypePtr()->isPointerType()) {
      continue;
    }

    /*begin check the i'th arg in caller */
    string parExprStr = par->getNameAsString();
    string result_caller = exitNullCheck(caller, parExprStr);
    if (result_caller == "")
      continue;

    /* check all the parents has the null check for i'th*/
    if (checkFunParents(caller, i) == false)
      continue;

    /* if all the parents has the null check for the i'th arg, output the
     * warning */
    vector<string> res = split(result_caller, ":");
    int lineNum = atoi(res[1].c_str());
    int colNum = atoi(res[2].c_str());
    ReportSpace::Defect *defect =
        new ReportSpace::Defect(ReportSpace::DefectName::RedundantRullCheck,
                                ReportSpace::DefectType::Warning, result_caller,
                                lineNum, colNum, parExprStr);
    defect->addDesc("RedudantNullCheck exists in function " +
                    caller->getName() + " for pointer " + parExprStr);
    defect->addDesc("Locaiton:  " + result_caller);
    external_bug_report.addToReport(defect);
  }
}

bool RedundantNullCheck::checkFunParents(ASTFunction *caller, int i) {
  /*begin check the caller's parents for i'th arg*/
  const FunctionDecl *callerDecl = manager->getFunctionDecl(caller);
  int j = 1;
  for (auto callerParent : call_graph->getParents(caller)) {
    /*
    cout << "check  " << caller->getName() << " 's  " << j << "/"
         << call_graph->getParents(caller).size()
         << "parent: " << callerParent->getName() << " for the " << i
         << " 'th argment" << endl;
    */
    j++;

    const FunctionDecl *callerParentDecl =
        manager->getFunctionDecl(callerParent);
    if (callerParentDecl == nullptr || callerParentDecl->hasBody() == false ||
        callerParentDecl->isInStdNamespace() == true) {
      return false;
    }

    const FunctionDecl *caller_parent_calleeDecl = nullptr;
    const CallExpr *caller_parent_callexpr = nullptr;

    std::unique_ptr<CFG> &cfg = manager->getCFG(callerParent);
    if (cfg == nullptr)
      return false;
    for (CFGBlock *block : *cfg) {
      if (block == nullptr)
        continue;
      for (auto element : *block) {
        if (element.getKind() == CFGElement::Statement) {
          const Stmt *stmt = element.castAs<CFGStmt>().getStmt();
          if (stmt == nullptr)
            continue;
          if (const CallExpr *callExpr = dyn_cast<CallExpr>(stmt)) {
            // check the callerExpr's callee is caller
            caller_parent_callexpr = callExpr; // record
            const Decl *decl = callExpr->getCalleeDecl();
            if (decl == nullptr)
              continue;
            if (decl->getKind() == clang::Decl::Var) {
              const VarDecl *varDecl = dyn_cast<VarDecl>(decl);
              if (varDecl->getType()->isFunctionPointerType() == false)
                continue;
              const Expr *exp = varDecl->getAnyInitializer(varDecl);
              if (exp == nullptr)
                continue;
              const UnaryOperator *uo_expr = dyn_cast<UnaryOperator>(exp);
              if (uo_expr == nullptr)
                continue;
              Expr *sub_expr = uo_expr->getSubExpr();
              DeclRefExpr *ref =
                  dyn_cast<DeclRefExpr>(sub_expr->IgnoreImpCasts());
              if (ref != nullptr) {
                Decl *refDecl = ref->getDecl();
                if (FunctionDecl *ref_type = dyn_cast<FunctionDecl>(refDecl)) {
                  caller_parent_calleeDecl = ref_type;
                  if (caller_parent_calleeDecl == nullptr ||
                      caller_parent_calleeDecl != callerDecl) {
                    continue;
                  } else {
                    break;
                  }
                }
              }
            }

            if (decl->getKind() == clang::Decl::Function) {
              const FunctionDecl *calleeDecl = callExpr->getDirectCallee();
              caller_parent_calleeDecl = calleeDecl;
              if (caller_parent_calleeDecl == nullptr ||
                  caller_parent_calleeDecl != callerDecl) {
                continue;
              } else {
                break;
              }
            }
          }
        }
      }
      if (caller_parent_calleeDecl != nullptr &&
          caller_parent_calleeDecl == callerDecl) {
        break;
      }
    }

    if (caller_parent_calleeDecl == nullptr ||
        caller_parent_calleeDecl != callerDecl ||
        caller_parent_callexpr == nullptr)
      return false;
    /* begin check the callerParent*/
    Expr *arg = const_cast<Expr *>(caller_parent_callexpr->getArg(i));
    if (arg == nullptr)
      return false;
    const QualType q = arg->getType();
    if (q.getTypePtr()->isPointerType() == false)
      return false;
    string argStr = getString_of_Expr(arg);
    string result_caller_parent = exitNullCheck(callerParent, argStr);
    if (result_caller_parent == "")
      return false;
    /*
    cout << " Finish check  " << caller->getName() << " 's  " << j - 1 << "/"
         << call_graph->getParents(caller).size()
         << "parent: " << callerParent->getName() << " for the " << i
         << " 'th argment" << endl;
    */
  }
  return true;
}

void RedundantNullCheck::check(PointToAnalysis *p) {
  readConfig();
  p2a = p;
  if (pointer_analysis == "true" && p2a == NULL) {
    cout << "[!] Enable PointToAnalysisChecker and set pointer_analysis to be "
            "true."
         << endl;
    return;
  }

  unsigned funcNums = manager->getFunctions(true).size();
  int i = 1;
  for(auto fun: manager->getFunctions(true)){
    process_bar(float(i)/funcNums);
    i++;
    const FunctionDecl *funDecl = manager->getFunctionDecl(fun);
    if (funDecl == nullptr || funDecl->hasBody() == false ||
        funDecl->isInStdNamespace() == true) {
      continue;
    }
    checkFun(fun);
  }

  //visitFromEntry();
}
